package com.pmdesigns.sqlmapper;

import java.io.*;
import java.util.*;
import com.pmdesigns.jvc.tools.JVCParser;
import com.pmdesigns.jvc.tools.ParseException;
import static com.pmdesigns.sqlmapper.Utils.*;

/**
 * Before the ModelGenerator can be run the classes for
 * parsing and processing the template files must be generated
 * and compiled.
 * <p>
 * This class parses all the template files in an indicated directory
 * and produces a model generator class for each one.  It uses the
 * same configuration file as the ModelGenerator.
 *
 * @author mike dooley
 */
public class BootstrapGenerator {
	// break into this comment into two lines so that this file doesnt get deleted by 'realclean'
	public static final String MACHINE_GENERATED_1 = "/* This is a machine generated file.";
	public static final String MACHINE_GENERATED_2 = " DO NOT EDIT. */";
	public static final String GENERATOR_PKG_PREFIX  = "generators";
	public static final String GENERATOR_METHOD_NAME = "genModel";

	File outDir;
	File templateDir;
	String templateSuffix = ".tpl";
	Map<String,File> templateMap;
	boolean force;
	boolean debug;
	
	
	public static void usage() {
		System.err.println("args : <config file> <output dir> [-force] [-debug]");
	}

	public static void main(String[] args) {
		if (args.length < 2 || args.length > 4) {
			System.err.println("Wrong number of args");
			usage();
			return;
		}

		boolean force = false;
		boolean debug = false;
		for (int i=2; i < args.length; i++) {
			if ("-debug".equals(args[i]))      debug = true;
			else if ("-force".equals(args[i])) force = true;
		}
		
		try {
			BootstrapGenerator generator = new BootstrapGenerator(args[0], args[1], force, debug);
			generator.generate();
			
		} catch (Exception e) {
			String msg = e.getMessage();
			System.err.println("Error: "+((msg != null) ? msg : e.toString()));
			if (debug) e.printStackTrace();
		}
	}

	public BootstrapGenerator(String configFile, String outputDir, boolean force, boolean debug) throws Exception {

		this.force = force;
		this.debug = debug;
		templateMap = new HashMap<String,File>();
		
		outDir = new File(outputDir);
		if (!outDir.exists()) {
			outDir.mkdirs();
		}
		loadConfigs(new File(configFile));

		System.out.println("Template generator configuration");
		System.out.println("           out dir:  "+outDir);
		System.out.println("      template dir:  "+templateDir);
		System.out.println("   template suffix:  "+templateSuffix);
		System.out.println("   templates found:  "+templateMap.size());
		System.out.println("       force regen:  "+force);
		System.out.println("             debug:  "+debug);
	}
	
	private void loadConfigs(File propsFile) throws Exception {
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(propsFile);
			Properties props = new Properties();
			props.load(fis);
			for (Enumeration e = props.propertyNames(); e.hasMoreElements(); ) {
				String key = (String) e.nextElement();
				String val = props.getProperty(key);
				if ("template.dir".equals(key)) {
					templateDir = new File(val);

				} else if ("template.suffix".equals(key)) {
					templateSuffix = val;
				}
			}

			if (templateDir == null) {
				throw new IllegalStateException("Missing template directory");
				
			} else if (!templateDir.isDirectory()) {
				throw new FileNotFoundException(templateDir+" is not a directory");
			}

			for (File f : templateDir.listFiles()) {
				if (f.isFile() && f.getName().endsWith(templateSuffix)) {
					String key = template2GeneratorName(f.getName());
					if (templateMap.get(key) != null) {
						throw new IllegalStateException("Multiple templates with same name '"+key+"'");
					}
					templateMap.put(key, f);
				}
			}

			if (templateMap.isEmpty()) {
				throw new IllegalStateException("No templates found in: "+templateDir);
			}

		} finally {
			if (fis != null) fis.close();
		}
	}

	public void generate() throws ParseException, IOException {

		String pkgPrefix = GENERATOR_PKG_PREFIX;
		File srcDir = new File(outDir, pkgPrefix.replace('.', File.separatorChar));
		if (!srcDir.exists()) {
			srcDir.mkdirs();
		}

		// create BaseGenerator class, if necessary
		File baseGen = new File(srcDir, "BaseGenerator.java");
		if (force || !baseGen.exists()) {
			PrintWriter pw = new PrintWriter(baseGen);
			pw.println(MACHINE_GENERATED_1+MACHINE_GENERATED_2);
			pw.println("package "+ pkgPrefix+";");
			pw.println("");
			pw.println("public class BaseGenerator {");
			pw.println("protected static String _nf(String x) { return (x == null) ? \"\" : x; }");
			pw.println("protected static Object _nf(Object x) { return (x == null) ? \"\" : x; }");
			pw.println("protected static int _nf(int x) { return x; }");
			pw.println("protected static short _nf(short x) { return x; }");
			pw.println("protected static long _nf(long x) { return x; }");
			pw.println("protected static float _nf(float x) { return x; }");
			pw.println("protected static double _nf(double x) { return x; }");
			pw.println("protected static char _nf(char x) { return x; }");
			pw.println("protected static byte _nf(byte x) { return x; }");
			pw.println("protected static boolean _nf(boolean x) { return x; }");
			pw.println("protected static String capitalize(String s) {");
			pw.println("  if (s == null || s.length() == 0) return s;");
			pw.println("  return Character.toUpperCase(s.charAt(0)) + s.substring(1);");
			pw.println("}");
			pw.println("protected static String uncapitalize(String s) {");
			pw.println("  if (s == null || s.length() == 0) return s;");
			pw.println("  return Character.toLowerCase(s.charAt(0)) + s.substring(1);");
			pw.println("}");
			pw.println("protected static String pluralize(String s) {");
			pw.println("  if (s == null || s.length() == 0) return s;");
			pw.println("  return s.endsWith(\"s\") ? s+\"es\" : s+\"s\";");
			pw.println("}");
			pw.println("}");
			pw.println("");
			pw.close();
		}
		
		TreeSet<String> imports = new TreeSet<String>();
		for (String name : templateMap.keySet()) {
			File f = templateMap.get(name);
			File outFile = new File(srcDir, name+".java");

			// check if the generator is out of date
			if (!force && outFile.exists() && outFile.lastModified() >= f.lastModified()) {
				continue;
			}

			// parse the view template
			imports.clear();
			JVCParser parser = new JVCParser(f.getAbsolutePath(), imports, debug);
			String body = parser.parse();

			// add extra imports
			imports.add("com.pmdesigns.sqlmapper.Model");
			imports.add("com.pmdesigns.sqlmapper.Field");
			imports.add("com.pmdesigns.sqlmapper.ForeignKey");
			imports.add("com.pmdesigns.sqlmapper.JoinedModels");

			// write the page generator
			PrintWriter pw = new PrintWriter(outFile);
			pw.println(MACHINE_GENERATED_1+MACHINE_GENERATED_2);
			pw.println("package "+ pkgPrefix+";");
			pw.println("");
			for (String pkg : imports) {
				pw.println("import "+pkg+";");
			}
			pw.println("");
			pw.println("public class "+name+" extends BaseGenerator {");
			pw.println("");
			pw.println("public static String "+GENERATOR_METHOD_NAME+"(Model model) {");
			pw.println("StringBuilder _sb = new StringBuilder();");
			pw.println(body);
			pw.println("return _sb.toString();");
			pw.println("}");
			pw.println("}");
			pw.close();

			System.out.println("Wrote "+outFile.getName());
		}
	}
}
